package org.hsieh.tr.explore.annotation;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

import org.hsieh.tr.explore.exception.ExceptionHandler;

/**
 * Help to parse annotation
 * 
 *@author hsieh 234aini@gmail.com
 * 
 */
@SuppressWarnings("unchecked")
public abstract class AnnotationParser {

    public static <T extends Annotation> T findAnnotation(Object obj, Class<T> t) {
	if (obj == null)
	    throw new IllegalArgumentException("Object");

	Class klass = obj.getClass();
	Parameter<Annotation> finalValue = new Parameter<Annotation>(null);
	Parameter<Boolean> find = new Parameter<Boolean>(Boolean.FALSE);
	while (klass != null) {
	    internalFind(t, klass, finalValue, find);
	    if (find.getValue()) {
		break;
	    }
	    klass = klass.getSuperclass();
	}
	return (T) finalValue.getValue();
    }

    private static <T extends Annotation> void internalFind(Class<T> t,
	    Class klass, Parameter<Annotation> finalValue,
	    Parameter<Boolean> find) {
	Annotation annotation = klass.getAnnotation(t);
	if (annotation != null) {
	    find.setValue(Boolean.TRUE);
	    finalValue.setValue(annotation);
	}

	Field[] declaredFields = klass.getDeclaredFields();
	for (Field field : declaredFields) {
	    if (field.isAnnotationPresent(t)) {
		annotation = field.getAnnotation(t);
	    }
	    if (annotation != null) {
		find.setValue(Boolean.TRUE);
		finalValue.setValue(annotation);
	    }
	}

	Method[] declaredMethods = klass.getDeclaredMethods();
	for (Method method : declaredMethods) {
	    if (method.isAnnotationPresent(t)) {
		annotation = method.getAnnotation(t);
		if (annotation != null) {
		    find.setValue(Boolean.TRUE);
		    finalValue.setValue(annotation);
		}
	    }
	}
    }

    /**
     * If the annotation target is a method, the method can be invoked with no
     * arguments.
     * 
     * @param <T>
     * @param obj
     * @param annotaion
     * @return
     */
    public static <T extends Annotation> Object getValueAnnotationWith(
	    Object obj, Class<T> annotaion) {
	if (obj == null) {
	    throw new IllegalArgumentException("Object");
	}

	Class klass = obj.getClass();
	Parameter<Object> finalValue = new Parameter<Object>(null);
	Parameter<Boolean> find = new Parameter<Boolean>(Boolean.FALSE);
	while (klass != null) {
	    find(obj, annotaion, klass, finalValue, find);
	    if (find.getValue()) {
		break;
	    }
	    klass = klass.getSuperclass();
	}

	return finalValue.getValue();
    }

    private static <T extends Annotation> void find(Object obj,
	    Class<T> annotaion, Class klass, Parameter<Object> finalValue,
	    Parameter<Boolean> find) {
	Field[] declaredFields = klass.getDeclaredFields();
	for (Field field : declaredFields) {
	    if (field.isAnnotationPresent(annotaion)) {
		find.setValue(Boolean.TRUE);
		boolean accessible = field.isAccessible();
		try {
		    if (!accessible) {
			field.setAccessible(true);
		    }
		    Object value = field.get(obj);
		    field.setAccessible(accessible);
		    if (value != null)
			finalValue.setValue(value);
		} catch (Exception e) {
		    ExceptionHandler.handle(e);
		}
	    }
	}

	if (!find.getValue()) {
	    Method[] declaredMethods = klass.getDeclaredMethods();
	    for (Method method : declaredMethods) {
		if (method.isAnnotationPresent(annotaion)) {
		    find.setValue(Boolean.TRUE);
		    boolean accessible = method.isAccessible();
		    try {
			if (!accessible)
			    method.setAccessible(Boolean.TRUE);
			Object invoke = method.invoke(obj, new Object[0]);
			method.setAccessible(accessible);
			if (invoke != null)
			    finalValue.setValue(invoke);
		    } catch (Exception e) {
			ExceptionHandler.handle(e);
		    }
		}
	    }
	}
    }
}
